using System;
using System.IO;
using BestHTTP;
namespace Networking.Runtime
{
    [Serializable]
    public struct DownLoadProcessData
    {
        public long downloadedSize;
        public int connectionCount;
        public bool isInit;
        public void Reset()
        {
            downloadedSize = 0;
            connectionCount = 0;
            isInit = false;
        }
    }

    public sealed class HttpFileDownLoadRequest : XNetObject
    {
        public string CacheFile => DownLoadSetting.DIR_DOWNLOAD_TEMP + "/" + DownLoadFileData.fileMd5;
        public Action<HttpFileDownLoadRequest> OnDownLoadFinish;
        public DownLoadProcessData InternalProcessData = new DownLoadProcessData();
        public DownLoadFileData DownLoadFileData { get; private set; }
        private HTTPRequest _request;
        public bool IsSuccess => IsFileDownloaded();

        public void BeginDownload(DownLoadFileData downLoadFileData)
        {
            DownLoadFileData = downLoadFileData;
            InternalProcessData.Reset();
            InternalProcessData.isInit = true;
            BeginDownload();
        }

        private void BeginDownload()
        {
            if (_disposed || !InternalProcessData.isInit)
            {
                return;
            }
            if (TryFinish())
            {
                return;
            }
            DownLoadExtensions.PrepareFileDirectory(CacheFile);
            DownLoadLoom.RunAsync(SendNewRequest);
        }

        private void SendNewRequest()
        {
            ReleaseRequest();
            InternalProcessData.connectionCount++;
            if (InternalProcessData.connectionCount >= DownLoadSetting.MAX_RETRIES)
            {
                OnDownLoadFinish?.Invoke(this);
                return;
            }
            int index = InternalProcessData.connectionCount % DownLoadFileData.fileUrl.Length;
            string url = DownLoadFileData.fileUrl[index];
            _request = new HTTPRequest(new Uri(url), DownLoadCallBack)
            {
                Tag = DownLoadFileData.fileMd5,
                StreamFragmentSize = DownLoadSetting.MAX_FRAME_DOWNLOAD,
                MaxRetries = DownLoadSetting.MAX_RETRIES,
                OnStreamingData = OnFrameDownLoad,
            };
            InternalProcessData.downloadedSize = GetCachedFileLength();
            _request.SetRangeHeader(InternalProcessData.downloadedSize);
            _request.Send();
        }


        private void DownLoadCallBack(HTTPRequest req, HTTPResponse resp)
        {
            switch (req.State)
            {
                case HTTPRequestStates.Initial:
                    break;
                case HTTPRequestStates.Processing:
                    break;
                case HTTPRequestStates.Finished:
                    if (!TryFinish())
                    {
                        DownLoadExtensions.DeleteFile(CacheFile);
                        BeginDownload();
                    }
                    break;
                case HTTPRequestStates.Aborted:
                    break;
                case HTTPRequestStates.Error:
                    OnTimeOut();
                    break;
                case HTTPRequestStates.ConnectionTimedOut:
                    OnTimeOut();
                    break;
                case HTTPRequestStates.TimedOut:
                    OnTimeOut();
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private bool OnFrameDownLoad(HTTPRequest request, HTTPResponse response, byte[] dataFragment, int dataFragmentLength)
        {
            lock (DownLoadSpeedWatcher.SpeedWatcher)
            {
                DownLoadSpeedWatcher.SpeedWatcher.RecordLoadSize(dataFragmentLength);
                InternalProcessData.downloadedSize += dataFragmentLength;
                using FileStream fsWrite = new FileStream(CacheFile, GetFileMode());
                {
                    fsWrite.Write(dataFragment, 0, dataFragmentLength);
                    fsWrite.Flush();
                    fsWrite.Close();
                }
            }
            return true;
        }
        
        private void OnTimeOut()
        {
            HTTPManager.RequestTimeout += TimeSpan.FromSeconds(DownLoadSetting.DEL_ADD_TIME_OUT);
            BeginDownload();
        }

        private FileMode GetFileMode()
        {
            return FileMode.OpenOrCreate | FileMode.Append;
        }

        private bool TryFinish()
        {
            if (IsFileDownloaded())
            {
                OnDownLoadFinish?.Invoke(this);
                return true;
            }

            if (IsCacheFileDownloaded())
            {
                DownLoadExtensions.MoveTo(CacheFile, DownLoadFileData.filePath);
                OnDownLoadFinish?.Invoke(this);
                return true;
            }
            return false;
        }

        private void ReleaseRequest()
        {
            if (_request != null)
            {
                _request.OnStreamingData = null;
                _request.OnDownloadProgress = null;
                _request.Abort();
                _request.Clear();
                _request.Dispose();
                _request = null;
            }
        }
        
        public void Clear()
        {
            InternalProcessData.Reset();
            ReleaseRequest();
        }

        protected override void OnDispose()
        {
            Clear();
            OnDownLoadFinish = null;
        }

        private long GetCachedFileLength()
        {
            return DownLoadExtensions.GetFileSize(CacheFile);
        }

        public bool IsFileDownloaded()
        {
            return DownLoadFileData.IsFileDownloaded();
        }

        private bool IsCacheFileDownloaded()
        {
            return DownLoadExtensions.IsFileDownloaded(CacheFile, DownLoadFileData.fileMd5);
        }
    }
}